package uuid

import (
	"fmt"
	"sync"
	"time"
)

const (
	SecondsBits  = 32 // 时间戳所占位数
	NodeIDBits   = 12 // 节点ID位数
	SequenceBits = 20 // 序列号

	MaxSeconds  = 1<<32 - 1 // 最大时间戳
	MaxNodeID   = 1<<10 - 1 // 最大节点ID
	MaxSequence = 1<<22 - 1 // 最大序列号

	SecondsMask  = 0xFFFFFFFF00000000 // 时间戳掩码
	SecondsShift = 32                 // 时间戳移位

	NodeIDMask  = 0x00000000FFF00000 // 节点ID掩码
	NodeIDShift = 20                 // 节点ID移位

	SequenceMask  = 0x00000000000FFFFF // 序号掩码
	SequenceShift = 0                  // 序号移位

	CustomEpoch = 1640966400 // 开始时间 2022-01-01 00:00:00 UTC
)

/*
	UUID二进制位分布如下:
	 		-------------------------------
	fileds  | Seconds | NodeID | Sequence |
	 		-------------------------------
	bytes   |    32   |   12   |    20    |
	 		-------------------------------
*/

type Snowflake struct {
	mutex       sync.Mutex
	nodeID      uint64 // 节点ID
	seq         uint64 // 上次分配的序列号
	lastSeconds uint64 // 上次分配时间
	lastUUID    uint64 // 上次分配的ID
}

func currentSeconds() uint64 {
	return uint64(time.Now().Unix() - CustomEpoch)
}

// 序列号分配完毕或系统时钟回拨时，循环等待系统时钟增长到上次分配时钟之后
func waitUntilNextSeconds(oldSeconds uint64) uint64 {
	for {
		time.Sleep(time.Millisecond * 10)
		nowSeconds := currentSeconds()
		if nowSeconds > oldSeconds {
			return nowSeconds
		}
	}
}

func NewSnowflake(nodeID uint16) *Snowflake {
	nodeID &= 0x03FF

	return &Snowflake{
		nodeID:      uint64(nodeID),
		seq:         0,
		lastSeconds: currentSeconds(),
		lastUUID:    0,
	}
}

func Encode(seconds, node, seq uint64) (uuid uint64) {
	bitSeconds := (seconds << SecondsShift) & SecondsMask
	bitNodeID := (node << NodeIDShift) & NodeIDMask
	bitSequence := (seq << SequenceShift) & SequenceMask
	uuid = bitSeconds | bitNodeID | bitSequence
	return
}

func Decode(uuid uint64) (int64, uint16, uint32) {
	timestamp := ((uuid & SecondsMask) >> SecondsShift) + CustomEpoch
	nodeID := (uuid & NodeIDMask) >> NodeIDShift
	sequence := (uuid & SequenceMask) >> SequenceShift
	return int64(timestamp), uint16(nodeID), uint32(sequence)
}

// 生成一个UUID，当时间戳位溢出时会panic
// 其次当系统时钟回拨时会循环等待系统时钟回到上次生成时间之后
func (sf *Snowflake) Generate() uint64 {
	sf.mutex.Lock()
	defer sf.mutex.Unlock()

	seconds := currentSeconds()

	if seconds < sf.lastSeconds {
		seconds = waitUntilNextSeconds(sf.lastSeconds)
	}

	if seconds == sf.lastSeconds {
		sf.seq++
		if sf.seq > MaxSequence {
			sf.seq = 0
			seconds = waitUntilNextSeconds(seconds)
		}
	} else {
		sf.seq = 0
	}

	if seconds > MaxSeconds {
		panic(fmt.Errorf("seconds %v overflow %v", seconds, MaxSeconds))
	}

	sf.lastSeconds = seconds
	sf.lastUUID = Encode(seconds, sf.nodeID, sf.seq)

	return sf.lastUUID
}
